package com.onpositive.dataminer.core;

import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.Platform;
import org.w3c.dom.Document;

import com.onpositive.dataminer.userstorage.UserValueStorage;
import com.onpositive.dataminer.value.audits.WikiLinksRedirectionAudit;
import com.onpositive.knowledge.model.ICanResolveWikiRefs;
import com.onpositive.knowledge.model.IHasVersions;
import com.onpositive.knowledge.model.IThing;
import com.onpositive.knowledge.model.IUniverse;
import com.onpositive.knowledge.model.impl.BasicUniverse;
import com.onpositive.knowledge.model.impl.BasicUniverseProperty;
import com.onpositive.knowledge.model.value.audits.AbstractAuditResult;
import com.onpositive.knowledge.model.value.audits.ActualDocumentsAudit;
import com.onpositive.knowledge.model.value.audits.DisambiguationValueAudit;
import com.onpositive.knowledge.model.value.audits.IReferenceLookup;
import com.onpositive.knowledge.model.value.audits.IValueAudit;
import com.onpositive.knowledge.model.value.audits.IdentAudit;
import com.onpositive.knowledge.model.value.audits.IdentValueSplitterAudit;
import com.onpositive.knowledge.model.value.audits.PredictionAudit;
import com.onpositive.knowledge.model.value.audits.ReferenceResolvingAudit;
import com.onpositive.knowledge.model.value.audits.StatisticValueAudit;
import com.onpositive.knowledge.model.value.audits.WikiLinksStatisticAudit;
import com.onpositive.semantic.model.api.meta.BaseMeta;
import com.onpositive.semantic.model.api.meta.DefaultMetaKeys;
import com.onpositive.semantic.model.api.meta.IMeta;
import com.onpositive.semantic.model.api.property.IProperty;
import com.onpositive.semantic.model.api.realm.IRealm;
import com.onpositive.semantic.model.api.realm.Realm;

public class DefaultUniverse extends BasicUniverse implements IHasVersions {

	/**
	 * 
	 */
	private static final long serialVersionUID = -5797970973159158080L;

	private final class MP extends BasicUniverseProperty implements IReferenceLookup{
		/**
		 * 
		 */
		private static final long serialVersionUID = 6451925416954394643L;

		private MP(IProperty userAjustment, BaseMeta m) {
			super(userAjustment, m);
			meta.registerService(IUniverse.class, DefaultUniverse.this);
			meta.registerService(IReferenceLookup.class, this);
		}

		protected void changed(String str, String key, String tag) {
			if( tag == null )
				savePropertyMeta(this);
			else
				saveAuditMeta(this);
			super.changed(str, key, null);
			if (!inInit) {
				for (IUniverse m : universes) {
					if (m instanceof IShouldBeNotified) {
						IShouldBeNotified km = (IShouldBeNotified) m;
						km.propertyChanged(str, key);
					}
				}
			}
		}

		@Override
		public BasicUniverse getUniverse() {
			return DefaultUniverse.this;
		}

		@Override
		public Object lookupRefs(Object source) {
			return resolveRef(getId(), source);
		}
	}

	ArrayList<IUniverse> universes = new ArrayList<IUniverse>();
	CountingCompositeRealm<IThing> mainRealm = new CountingCompositeRealm<IThing>();

	protected HashMap<String, BasicUniverseProperty> props = new HashMap<String, BasicUniverseProperty>();
	protected UserValueStorage userValueStorage = new UserValueStorage(this);

	@Override
	public IProperty getProperty(Object obj, String name) {		
		return props.get(name);
	}

	@SuppressWarnings("unchecked")
	public DefaultUniverse() {
		inInit = true;

		try {
			File fl = getDataLocation("userValues");
			File file = new File(fl, "props.xml");
			if (file.exists()) {
				Document parse = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(file);
				Collection<BaseMeta> metas2 = new PropertyMetaPersistance().metas(parse);
				for (BaseMeta m : metas2) {
					String stringValue = DefaultMetaKeys.getStringValue(m,DefaultMetaKeys.ID_KEY);
					if (stringValue==null){
						continue;
					}
					BasicUniverseProperty  basicUniverseProperty = new MP(
							userValueStorage.getProperty(null, stringValue), m);
					props.put(stringValue, basicUniverseProperty);
					((Realm) super.allProperties()).add(basicUniverseProperty);
					if (allProperties().contains(null)){
						throw new IllegalStateException();
					}
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
				
		try {
			File fl = getDataLocation("auditValues");
			File file = new File(fl, "props.xml");
			if (file.exists()) {
				Document parse = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(file);
				Collection<BaseMeta> metas2 = new PropertyMetaPersistance().metas(parse);
				for (BaseMeta m : metas2) {
					String propName = DefaultMetaKeys.getStringValue(m,DefaultMetaKeys.ID_KEY);
					if (propName==null){
						continue;
					}
					BasicUniverseProperty  basicUniverseProperty = props.get(propName) ;
					if( basicUniverseProperty == null ){
						
						basicUniverseProperty= new MP(userValueStorage.getProperty(null, propName), null);						
						props.put(propName, basicUniverseProperty);
						((Realm) super.allProperties()).add(basicUniverseProperty);
					}					
					basicUniverseProperty.copyParentMeta(m) ;
					
					if (allProperties().contains(null)){
						throw new IllegalStateException();
					}
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			inInit = false;
		}
		
		
	}

	@Override
	protected IRealm<IThing> createRealm() {
		return mainRealm;
	}

	static DefaultUniverse instance = new DefaultUniverse();

	public static DefaultUniverse getInstance() {
		return instance;
	}

	boolean inInit;
	private HashMap<String,File> storageFiles = new HashMap<String, File>();
	private File serializationFile;

	@SuppressWarnings("unchecked")
	public synchronized void mount(IDataProvider createProvider) {
		inInit = true;
		try {
			IUniverse universe = createProvider.getUniverse();
			if( universe == null ){
				inInit = false;
				return ;
			}
			universes.add(universe);
			IRealm<IThing> all = universe.all();
			IRealm<? extends IProperty> allProperties = universe
					.allProperties();
			for (IProperty p : allProperties) {
				
				BasicUniverseProperty basicUniverseProperty = props.get(p.getId());
				if (basicUniverseProperty != null) {
					basicUniverseProperty.merge(p);
				} else {
					basicUniverseProperty = new MP(
							userValueStorage.getProperty(null, p.getId()), null);
					
					props.put(p.getId(), basicUniverseProperty);
					basicUniverseProperty.merge(p);
					((Realm) super.allProperties()).add(basicUniverseProperty);
				}
			}
			File fl1;
			try {
				fl1 = new File(getDataLocation("userValues"), "values.xml");
				userValueStorage.setFile(fl1);
				mainRealm.addRealm(all);
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (URISyntaxException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

		}
		catch( Exception e ){
			e.printStackTrace() ;
		}
		finally {
		
			inInit = false;
			initAudit() ;
		}
	}

	protected synchronized void savePropertyMeta(BasicUniverseProperty basicUniverseProperty) {
		if (inInit)
			return ;
		
		PropertyMetaPersistance propertyMetaPersistance = new PropertyMetaPersistance();
		ArrayList<BaseMeta> ms = new ArrayList<BaseMeta>();
		for (BasicUniverseProperty v : props.values()) {
			BaseMeta meta = (BaseMeta) v.getMeta();
			if (meta.declaredKeys().size() > 1) {
				ms.add(meta);
			}
		}
		Document tranformToDoc = propertyMetaPersistance.tranformToDoc(ms);
		try {
			File fl = getDataLocation("userValues");
			Transformer newTransformer = TransformerFactory.newInstance()
					.newTransformer();
			newTransformer.setOutputProperty(OutputKeys.INDENT, "yes");
			newTransformer.transform(new DOMSource(tranformToDoc),
					new StreamResult(new File(fl, "props.xml")));
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	protected synchronized void saveAuditMeta(BasicUniverseProperty basicUniverseProperty) {
		if (inInit)
			return ;
		
		PropertyMetaPersistance propertyMetaPersistance = new PropertyMetaPersistance();
		ArrayList<BaseMeta> ms = new ArrayList<BaseMeta>();
		for (BasicUniverseProperty v : props.values()) {
			BaseMeta auditMeta = (BaseMeta) v.getMeta().getParentMeta();
			if (auditMeta.declaredKeys().size() > 1) {
				ms.add(auditMeta);
			}
		}
		Document tranformToDoc = propertyMetaPersistance.tranformToDoc(ms);
		try {
			File fl = getDataLocation("auditValues");
			Transformer newTransformer = TransformerFactory.newInstance()
					.newTransformer();
			newTransformer.setOutputProperty(OutputKeys.INDENT, "yes");
			newTransformer.transform(new DOMSource(tranformToDoc),
					new StreamResult(new File(fl, "props.xml")));
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	protected File getDataLocation( String str ) throws IOException, URISyntaxException {
		
		File storageFile = storageFiles.get(str) ;
		if (storageFile != null) {
			return storageFile;
		}
		
		URL dataArea = Platform.getInstanceLocation().getDataArea( str );
		URL fileURL = FileLocator.toFileURL(dataArea);
		storageFile = new File(fileURL.toURI());
		storageFile.mkdirs();
		
		storageFiles.put(str, storageFile) ;
		return storageFile;
	}
	
	public File getSerializationLocation() throws IOException, URISyntaxException {
		
		if (serializationFile != null) {
			return serializationFile;
		}
		
		URL dataArea = Platform.getInstanceLocation().getDataArea( "serialized" );
		URL fileURL = FileLocator.toFileURL(dataArea);
		serializationFile = new File(fileURL.toURI());
		serializationFile.mkdirs();
		return serializationFile;
		
	}


	public void unmount(IDataProvider d) {
		IUniverse universe = d.getUniverse();
		universes.remove(universe);
		IRealm<IThing> all = universe.all();
		mainRealm.removeRealm(all);
		IRealm<? extends IProperty> allProperties = universe.allProperties();
		for (IProperty p : allProperties) {
			BasicUniverseProperty basicUniverseProperty = props.get(p.getId());
			if (basicUniverseProperty != null) {
				basicUniverseProperty.remove(p);
				if (basicUniverseProperty.isEmpty()) {
					props.remove(p.getId());
					((Realm) super.allProperties())
							.remove(basicUniverseProperty);
				}
			}
		}
	}

	public Collection<IUniverse> getSubUniverses() {
		return new ArrayList<IUniverse>(universes);
	}
	IsInterestingThingAudit isInteresting=new IsInterestingThingAudit();
	
	@SuppressWarnings({ "unchecked", "rawtypes" })
	@Override
	protected Collection getGeneralAuditors() {
		return Collections.singletonList(isInteresting);
	}
	HasEnoughData data=new HasEnoughData();
	@Override
	protected void processGeneralResult(AbstractAuditResult perform,
			IValueAudit a) {
		if (perform instanceof HasEnoughData){
			this.data=(HasEnoughData) perform;
		}
		super.processGeneralResult(perform, a);
	}
	
	@Override
	protected void initAudit() {
		
		if( auditManager == null )
			initAuditManager();
		
		auditManager.addAudit(new PropertyRateAudit());
		
		auditManager.addAudit( new ActualDocumentsAudit() ) ;
		auditManager.addAudit( new ReferenceResolvingAudit() ) ;
		auditManager.addAudit( new WikiLinksStatisticAudit() ) ;
		auditManager.addAudit( new DisambiguationValueAudit() );
		auditManager.addAudit( new WikiLinksRedirectionAudit() ) ;

		//auditManage.addAudit( new ContradictionsDetectionAudit() ) ;
		
		auditManager.addAudit( new IdentAudit() ) ;
		auditManager.addAudit( new IdentValueSplitterAudit() ) ;
		auditManager.addAudit( new PredictionAudit() ) ;
		auditManager.addAudit( new StatisticValueAudit() );
	}

	public boolean isInteresting(Object o) {
		if (o instanceof IThing){
			IThing s=(IThing) o;
			return !s.isDepricated();
		}
		return !data.isSuspicios(o);
	}

	public HasEnoughData getEnoughDataAuditResult() {
		return data;
	}
	
	public void resolveWikipediaRefs(){
		
		for( IUniverse u : universes )
			if( u instanceof ICanResolveWikiRefs )
				((ICanResolveWikiRefs)u).resolveWikipediaRefs() ;			

	}
	
	public Object resolveRef(String id,Object m){
		
		for( IUniverse u : universes )
			if( u instanceof ICanResolveWikiRefs ){
				Object resolveRefs = ((ICanResolveWikiRefs)u).resolveRefs(id, m);
				if (resolveRefs!=null){
					return resolveRefs;
				}
			}
		return null;
	}

	@Override
	public void initAuditManager() {
		this.auditManager = new AuditManager( this ) ;		
	}

	@Override
	public boolean save() {
		
		boolean res = true ;
		for( IUniverse u : this.universes )
			res |= u.save() ;
		
		return res;
	}

	@Override
	public void contributeStandard(IThing thing, IProperty prop) {}

	@Override
	public Collection<? extends Object> getDelta(IProperty prop) {
		
		ArrayList<Object> result = new ArrayList<Object>() ;
		for( IThing th : all() )
		{
			Object delta = getDelta(th, prop) ;
			if( delta != null )
				result.add(delta) ;
		}
		return result ;		
	}

	@Override
	public Object getDelta(IThing thing, IProperty prop) {
		
		for( IUniverse universe : this.universes )
		{
			if ( !(universe instanceof IHasVersions) )
				continue ;
			
			IHasVersions ihv = (IHasVersions) universe;
			Object delta = ihv.getDelta(thing, prop) ;
			if(delta == null)
				continue;
			
			return delta ;
		}
		return null ;
	}

	BaseMeta meta;
	@Override
	public IMeta getMeta() {
		if(meta==null)
			meta = new BaseMeta();
		
		return meta;
	}
}
